package common.data;

import server.messageContent.TransferFundMsgContent;
import server.messageContent.BalanceEnquiryMsgContent;
import server.messageContent.CloseAccountMsgContent;
import server.messageContent.CreateAccountMsgContent;
import server.messageContent.DepositMsgContent;
import server.messageContent.MonitorMsgContent;
import server.messageContent.WithdrawMsgContent;
import common.Constants;
import common.SerializationManager;
import common.DeserializationManager;
import common.interfaces.MessageContent;

public class Message {
	//Message header
	protected MessageTypeEnum messageType;
	protected long requestId;
	
	//Message content
	protected RequestTypeEnum requestType;
	
	private MessageContent msgContent;
	private String msgContentString;

	//Message reply response object
	private Object responseObject;
	
	protected int length;

	public Message(byte[] data) {
		int currentIndex = 0;
		byte[] messageTypeStream = new byte[Constants.INT_LENGTH];
		System.arraycopy(data, currentIndex, messageTypeStream, 0, Constants.INT_LENGTH);
		messageType = MessageTypeEnum.values()[DeserializationManager.getCurrent().toInt(messageTypeStream)];
		currentIndex += Constants.INT_LENGTH;
		
		byte[] requestIdStream = new byte[Constants.LONG_LENGTH];
		System.arraycopy(data, currentIndex, requestIdStream, 0, Constants.LONG_LENGTH);
		requestId = DeserializationManager.getCurrent().toLong(requestIdStream);
		currentIndex += Constants.LONG_LENGTH;
		if (messageType == MessageTypeEnum.REQUEST){
			//can get request type
			byte[] requestTypeStream = new byte[Constants.INT_LENGTH];
			System.arraycopy(data, currentIndex, requestTypeStream, 0, Constants.INT_LENGTH);
			requestType = RequestTypeEnum.values()[DeserializationManager.getCurrent().toInt(requestTypeStream)];
			currentIndex += Constants.INT_LENGTH;
			
			int msgContentLength = data.length 
	                - Constants.INT_LENGTH //message type
	                - Constants.LONG_LENGTH //request ID
	                - Constants.INT_LENGTH;//request type
			
			byte[] msgContent = new byte[msgContentLength];
			System.arraycopy(data, currentIndex, msgContent, 0, msgContentLength);
			
			//TODO: classify msgContent
			switch (requestType) {
				case CREATE_ACCOUNT:
					this.msgContent = new CreateAccountMsgContent(msgContent);
					break;
				case CLOSE_ACCOUNT:
					this.msgContent = new CloseAccountMsgContent(msgContent);
					break;
				case DEPOSIT_ACCOUNT:
					this.msgContent = new DepositMsgContent(msgContent);
					break;
				case WITHDRAW_ACCOUNT:
					this.msgContent = new WithdrawMsgContent(msgContent);
					break;
				case BALANCE_ENQUIRY:
					this.msgContent = new BalanceEnquiryMsgContent(msgContent);
					break;
				case TRANSFER_SERVICE:
					this.msgContent = new TransferFundMsgContent(msgContent);
					break;
				case MONITOR_SERVER:
					this.msgContent = new MonitorMsgContent(msgContent);
					break;
			default:
				break;
			}
		}
		else { //message type is REPLY
			byte[] responseTypeStream = new byte[Constants.INT_LENGTH];
			System.arraycopy(data, currentIndex, responseTypeStream, 0, Constants.INT_LENGTH);
			ResponseTypeEnum responseType = DeserializationManager.getCurrent().toResponseTypeEnum(responseTypeStream);
			currentIndex += Constants.INT_LENGTH;
			
			//Deserialize the response message
			byte[] msgContentStreamLength = new byte[Constants.INT_LENGTH];
			System.arraycopy(data, currentIndex, msgContentStreamLength, 0, Constants.INT_LENGTH);
			int contentStreamLength = DeserializationManager.getCurrent().toInt(msgContentStreamLength);
			currentIndex += Constants.INT_LENGTH;
			byte[] msgContentStream = new byte[contentStreamLength];
			System.arraycopy(data, currentIndex, msgContentStream, 0, contentStreamLength);
			currentIndex += contentStreamLength;
			msgContentString = DeserializationManager.getCurrent().toString(msgContentStream);
			
			//deserialize the response object, which is only available if responseType is SUCCESSFUL
			if (responseType == ResponseTypeEnum.SUCCESSFUL){
				byte[] responseLengthStream = new byte[Constants.INT_LENGTH];
				System.arraycopy(data, currentIndex, responseLengthStream, 0, Constants.INT_LENGTH);
				currentIndex += Constants.INT_LENGTH;
				int responseLength = DeserializationManager.getCurrent().toInt(responseLengthStream);
				
				byte[] responseObjectStream = new byte[responseLength];
				System.arraycopy(data, currentIndex, responseObjectStream, 0, responseLength);
				
				switch(responseLength){
				case 8: //response object is long
					this.responseObject = DeserializationManager.getCurrent().toLong(responseObjectStream);
					System.out.println("Response is long " + this.responseObject);
					break;
				case 4: //response object is float or int. In this application, only float was returned from server
					this.responseObject = DeserializationManager.getCurrent().toFloat(responseObjectStream);
					System.out.println("Response is float " + this.responseObject);
					break;
				case 1: //response object is boolean or char. In this application, only bool was return from server
					this.responseObject = DeserializationManager.getCurrent().toBool(responseObjectStream);
					//System.out.println("Response is boolean " + this.responseObject);
					break;
					
				default:
					this.responseObject = DeserializationManager.getCurrent().toString(responseObjectStream);
					//System.out.println("Response is string " + this.responseObject);
					break;
				}
			}
			else {
				System.out.println("Response type is ERROR");
			}
		}
	}
	
	public Message(MessageTypeEnum messageType, long requestId,
			RequestTypeEnum requestType, MessageContent msgContent) {
		this.setMessageType(messageType);
		this.setRequestId(requestId);
		this.setRequestType(requestType);
		this.setMsgContent(msgContent);
	}

	public Message(MessageTypeEnum messageType, long requestId,
			MessageContent msgContent) {
		this.setMessageType(messageType);
		this.setRequestId(requestId);
		this.setMsgContent(msgContent);
	}

	public Message() {
		// TODO Auto-generated constructor stub
	}

	public MessageTypeEnum getMessageType() {
		return messageType;
	}

	public void setMessageType(MessageTypeEnum messageType) {
		this.messageType = messageType;
	}

	public long getRequestId() {
		return requestId;
	}

	public void setRequestId(long requestId) {
		this.requestId = requestId;
	}

	public MessageContent getMsgContent() {
		return msgContent;
	}

	public void setMsgContent(MessageContent msgContent) {
		this.msgContent = msgContent;
	}

	public String getMsgContentString() {
		return this.msgContentString;
	}
	
	public byte[] serialize() {
		byte[] messageTypeStream = SerializationManager.getCurrent()
				.fromMessageTypeEnum(messageType);
		byte[] requestIdStream = SerializationManager.getCurrent().fromLong(
				requestId);
		byte[] requestTypeStream = null;
		
		byte[] msgContentStream = msgContent.getByte();
		
		byte[] result;
		if (messageType == MessageTypeEnum.REQUEST) {
			requestTypeStream = SerializationManager.getCurrent()
					.fromRequestTypeEnum(requestType);
			result = new byte[messageTypeStream.length 
			                  +requestIdStream.length 
			                  + requestTypeStream.length 
			                  + msgContentStream.length];
			
		}
		else {
			result = new byte[messageTypeStream.length
			                  + requestIdStream.length 
			                  + msgContentStream.length];
		}
		
		
		int currentIndex = 0;
		System.arraycopy(messageTypeStream, 0, result, currentIndex,
				messageTypeStream.length);
		currentIndex += messageTypeStream.length;

		System.arraycopy(requestIdStream, 0, result, currentIndex,
				requestIdStream.length);
		currentIndex += requestIdStream.length;

		if (messageType == MessageTypeEnum.REQUEST) {
			System.arraycopy(requestTypeStream, 0, result, currentIndex,
					requestTypeStream.length);
			currentIndex += requestTypeStream.length;
		}
		System.arraycopy(msgContentStream, 0, result, currentIndex,
				msgContentStream.length);
		this.length = result.length;
		return result;
	}

	public int getLength() {
		return this.length;
	}

	public RequestTypeEnum getRequestType() {
		return requestType;
	}

	public void setRequestType(RequestTypeEnum requestType) {
		this.requestType = requestType;
	}

	public Object getResponseObject() {
		return responseObject;
	}

	public void setResponseObject(Object responseObject) {
		this.responseObject = responseObject;
	}
}
